import wx
import sys
import os
import inspect
import wx.xrc as xrc
import wx.aui as aui
try:
    from wx import glcanvas
    haveGLCanvas = True
except ImportError:
    haveGLCanvas = False

try:
    from OpenGL.GL import *
    from OpenGL.GLUT import *
    haveOpenGL = True
except ImportError:
    haveOpenGL = False

class Ctrls(object):
    '''
    class for container all controls defined in Case's xrcfile
    '''
    def __init__(self, ctrls):
        #before call this __init__, the Case's xrcfile is loaded and
        #created on the CasePanel, so we can find contrls by name and
        #make each one a data member of this class
        global app
        casepanel = app.casepanel
        if not casepanel:return
        for name in ctrls:
            ctrl = casepanel.FindWindowByName(name)
            if ctrl:
                setattr(self, name, ctrl)

class ICase(wx.EvtHandler):
    '''
    Case is the render helping interface
    '''
    def __init__(self, dummy=None):
        global app
        wx.EvtHandler.__init__(self)
        self.init = False
        self.canvas = None
        if dummy!=None:return
        #ctrls will be a class contained all controls defined in
        #xrcfile of one Case
        self.ctrls = Ctrls(Case.widgets)

    def OnPaint(self, event):
        if not self.init:
            self.InitGL()
            self.init = True
        self.Render()

    def Update(self):
        self.canvas.Update()

    def Render(self):
        pass

    def InitGL(self):
        pass

    def OnSize(self, event):
        pass

    def OnMouseUp(self, event):
        pass

    def OnMouseDown(self, event):
        pass

    def OnMouseMotion(self, event):
        pass

    def OnRMouseUp(self, event):
        pass

    def OnRMouseDown(self, event):
        pass

class RotorCanvas(glcanvas.GLCanvas):
    '''
    RotorCanvas is a GL canvas inherit from wxpython's glcanvas.
    it use a 'Case' interface for helping render and if you don't
    give such a interface, it use a default(very simple do noting)
    '''
    def __init__(self, parent, id, pos, sz, style):
        glcanvas.GLCanvas.__init__(self, parent, id, pos, sz, style)
        self.case = ICase('')
        self.case.canvas = self
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
        self.Bind(wx.EVT_SIZE            , self.OnSize)
        self.Bind(wx.EVT_PAINT           , self.OnPaint)
        self.Bind(wx.EVT_LEFT_DOWN       , self.OnMouseDown)
        self.Bind(wx.EVT_LEFT_UP         , self.OnMouseUp)
        self.Bind(wx.EVT_RIGHT_DOWN      , self.OnRMouseDown)
        self.Bind(wx.EVT_RIGHT_UP        , self.OnRMouseUp)
        self.Bind(wx.EVT_MOTION          , self.OnMouseMotion)

    def OnEraseBackground(self, event):
        pass

    def SetCase(self, case):
        self.case = case

    def OnMouseDown(self, event):
        self.case.OnMouseDown(event)
        event.Skip()

    def OnMouseUp(self, event):
        self.case.OnMouseUp(event)
        event.Skip()

    def OnRMouseDown(self, event):
        self.case.OnRMouseDown(event)
        event.Skip()

    def OnRMouseUp(self, event):
        self.case.OnRMouseUp(event)
        event.Skip()

    def OnMouseMotion(self, event):
        self.case.OnMouseMotion(event)
        event.Skip()

    def OnSize(self, event):
        size = self.GetClientSize()
        if self.GetContext():
            self.SetCurrent()
            glViewport(0, 0, size.width, size.height)
            if self.case and self.case.OnSize:
                self.case.OnSize(event)
        event.Skip()

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        self.SetCurrent()
        self.case.OnPaint(event)
        self.SwapBuffers()
        event.Skip()

class RotorXmlHandler(xrc.XmlResourceHandler):
    '''
    RotorCanvasXmlHandler is for handler custom controls in XRC.
    we use this handler to process any custom controls we need
    through a map 'ctors' which key:value is 'ClassName':controlclass
    '''
    def __init__(self, ctors):
        xrc.XmlResourceHandler.__init__(self)
        self.ctors = ctors
        self.currentclass = None
        #self.AddStyle('wxALL', wx.ALL)
        #self.AddStyle('wxEXPAND', wx.EXPAND)
        self.AddWindowStyles()

    def CanHandle(self, node):
        for k,v in self.ctors.iteritems():
            if self.IsOfClass(node, k):
                self.currentclass = v
                return True
        self.currentclass = None
        return False

    def DoCreateResource(self):
        assert self.GetInstance() is None
        assert self.currentclass
        control = self.currentclass(self.GetParentAsWindow(),
                self.GetID(),
                self.GetPosition(),
                self.GetSize(),
                self.GetStyle('style', wx.TAB_TRAVERSAL|wx.ALL|wx.EXPAND),
                )
        control.SetName(self.GetName())
        self.SetupWindow(control)
        self.CreateChildren(control)
        return control

class CaseTreeCtrl(wx.TreeCtrl):
    def __init__(self, parent, id, pos, sz, style):
        wx.TreeCtrl.__init__(self, parent, id, pos, sz, style)
        #icons setup
        isz = (16,16)
        il = wx.ImageList(isz[0], isz[1])
        self.fldridx     = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FOLDER,      wx.ART_OTHER, isz))
        self.fldropenidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN,   wx.ART_OTHER, isz))
        self.fileidx     = il.Add(wx.ArtProvider_GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, isz))
        self.SetImageList(il)
        self.il = il
        #add root item
        self.root = self.AddRoot('cases')
        self.SetItemImage(self.root, self.fldridx, wx.TreeItemIcon_Normal)
        self.SetItemImage(self.root, self.fldropenidx, wx.TreeItemIcon_Expanded)
        self.Bind(wx.EVT_RIGHT_UP, self.OnRMouseUp)
        #popup menus
        self.menustate = {
                # id:(name, callback, enable_sel_dir, enable_sel_case, enable_sel_mul)
                wx.NewId():('New dir'  , self.OnNewDir  , True , False, False),
                wx.NewId():('New case' , self.OnNewCase , True , True , False),
                wx.NewId():('Load case', self.OnLoadCase, False, True , False),
                wx.NewId():None        , 
                wx.NewId():('Delete'   , self.OnDel     , True , True , True)
                }
        self.menu      = self.CreatePopupMenu()
        self.map       = {}
        self.casepanel = None

    def CreatePopupMenu(self):
        menu = wx.Menu()
        for k, v in self.menustate.iteritems():
            if not v:
                menu.AppendSeparator()
            else:
                menu.Append(k, v[0])
                self.Bind(wx.EVT_MENU, v[1], id=k)
        return menu

    def OnNewDir(self, event):
        print('New dir')

    def OnDel(self, event):
        print('Delete dir')

    def OnNewCase(self, event):
        print('New case')

    def OnLoadCase(self, event):
        global app
        sels = self.GetSelections()
        assert len(sels) == 1
        casepath = self.GetPyData(sels[0])
        execfile(casepath, globals(), globals())
        #load xrc if have
        if Case.xrcfile and len(Case.xrcfile) > 0:
            self.LoadCaseXRC(casepath)
        else:
            app.SetCasePanel()
        case = Case()
        case.canvas = app.canvas
        app.SetCase(case)

    def LoadCaseXRC(self, casepath):
        global app
        xrcroot = os.path.dirname(casepath)
        xrcpath = os.path.join(xrcroot, Case.xrcfile)
        xml     = xrc.XmlResource(xrcpath)
        if not xml:
            app.SetCasePanel()
            return
        xml.InitAllHandlers()
        if Case.ctors and len(Case.ctors) > 0:
            xml.InsertHandler(RotorXmlHandler(Case.ctors))
        app.SetCasePanel(xml, Case.widgets[0])

    def ShowMenu(self, sel, sels, seldir, selcase, selmul):
        assert self.menu
        items = self.menu.GetMenuItems()
        for item in items:
            if item.IsSeparator():
                continue
            id = item.GetId()
            t  = self.menustate[id]
            assert t
            enable = ((seldir == t[2]) or (selcase == t[3])) and (selmul == t[4])
            item.Enable(enable)
        self.PopupMenu(self.menu)

    def OnRMouseUp(self, event):
        res = self.HitTest(event.GetPosition())
        if not res[0]:
            return
        sel     = res[0]
        sels    = self.GetSelections()
        seldir  = True
        selcase = True
        selmul  = len(sels) > 1
        for item in sels:
            isdir   = os.path.isdir(self.GetPyData(item))
            iscase  = not isdir
            seldir  = seldir and isdir
            selcase = selcase and iscase
        self.ShowMenu(sel, sels, seldir, selcase, selmul)

    def AppendDir(self, parent, name):
        item = self.AppendItem(parent, name)
        assert item
        self.SetItemImage(item, self.fldridx, wx.TreeItemIcon_Normal)
        self.SetItemImage(item, self.fldropenidx, wx.TreeItemIcon_Expanded)
        path = os.path.join(self.GetPyData(parent), name)
        self.SetPyData(item, path)
        self.map[path] = item
        return item

    def AppendCase(self, parent, name):
        item = self.AppendItem(parent, name)
        assert item
        self.SetItemImage(item, self.fileidx, wx.TreeItemIcon_Normal)
        path = os.path.join(self.GetPyData(parent), name)
        self.SetPyData(item, path)
        self.map[path] = item
        return item

    def NewDir(self, parent, name):
        pass

    def NewCase(self, parent, name):
        pass

    def Update(self):
        '''refresh cases dir'''
        if not self.casedir:
            return
        self.DeleteChildren(self.root)
        self.map = {}
        self.map[self.casedir] = self.root
        for root, dirs, files in os.walk(self.casedir):
            rootitem = self.map[root]
            for dir in dirs:
                self.AppendDir(rootitem, dir)
            for file in files:
                if file.endswith('.py'):
                    self.AppendCase(rootitem, file)

    @property
    def casedir(self):
        return self.GetPyData(self.root)

    @casedir.setter
    def casedir(self, path):
        self.SetPyData(self.root, path)
        self.map[path] = self.root



class RotorApp(wx.App):
    def OnInit(self):
        #get location of Rotor
        selfmod   = inspect.getmodule(self)
        self.path = os.path.dirname(inspect.getfile(selfmod))

        ctors = {
                'RotorCanvas'  : RotorCanvas,
                'CaseTreeCtrl' : CaseTreeCtrl
                }
        xml = xrc.XmlResource('RotorOpenGL.xrc')
        xml.InitAllHandlers()
        xml.InsertHandler(RotorXmlHandler(ctors))
        self.xml   = xml
        self.mgr   = aui.AuiManager()
        self.frame = xml.LoadFrame(None, 'MainFrame')
        self.mgr.SetManagedWindow(self.frame)
        self.panel = xml.LoadPanel(self.frame, 'MainPanel')
        self.panel.Show(True)
        self.mgr.AddPane(self.panel, aui.AuiPaneInfo().
                Name('MainPanel').Caption('MainPanel').
                CenterPane()
                )
        self.canvas    = self.panel.FindWindowByName('Canvas')

        self.treepanel = xml.LoadPanel(self.frame, 'TreePanel')
        self.treepanel.Show(True)
        self.casetree  = self.treepanel.FindWindowByName('CaseTree')
        self.casetree.casedir = os.path.join(self.path, 'cases')
        self.casetree.Update()
        self.mgr.AddPane(self.treepanel, aui.AuiPaneInfo().
                Name('TreePanel').Caption('Cases').
                Left().CloseButton(False).MaximizeButton(True).
                Layer(1)
                )
        self.casepanel = None
        self.SetCasePanel(xml, 'CasePanel')

        self.logpanel = xml.LoadPanel(self.frame, 'LogPanel')
        self.logctrl  = self.logpanel.FindWindowByName('LogCtrl')
        self.logpanel.Show(True)
        self.mgr.AddPane(self.logpanel, aui.AuiPaneInfo().
                Name('LogPanel').Caption('Log').
                Bottom().CloseButton(False).Layer(0)
                )
        self.mgr.Update()
        self.frame.Show(True)
        sys.stdout = sys.stderr = self.logctrl
        return True

    def SetCasePanel(self, xml=None, name=None):
        if self.casepanel:
            self.mgr.DetachPane(self.casepanel)
            self.casepanel.Destroy()
            self.casepanel = None
        if not xml or not name:
            self.mgr.Update()
            return
        self.casepanel = xml.LoadPanel(self.frame, name)
        self.casepanel.Show(True)
        self.mgr.AddPane(self.casepanel, aui.AuiPaneInfo().
                Name('CasePanel').Caption('CaseTools').
                Right().CloseButton(False).Layer(0)
                )
        self.mgr.Update()

    def SetCase(self, case):
        self.canvas.SetCase(case)

app = RotorApp(redirect=False)
app.MainLoop()
